﻿/** 
 * Copyright (c) 2001 University of Maryland, College Park, MD 20742, USA
 * Copyright (c) 2005 In4Arch
 *
 *  The MIT License
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of this 
 *  software and associated documentation files (the "Software"), to deal in the Software 
 *  without restriction, including without limitation the rights to use, copy, modify, merge, 
 *  publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 
 *  to whom the Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all copies 
 *  or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 *  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 *  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 *  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 *  OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: Benjamin B. Bederson and Martin Wattenberg 2001 
 * http://www.cs.umd.edu/hcil/treemap-history/index.shtml
 * 
 * Author: moedusa moedusa@gmail.com 2005
 * http://moedusa.name/
 *
 * Author: Nikita Shlyakhov shlyakhov@mail.ru 2006
 **/

import in4arch.alg.treemap.MapLayout; 
import in4arch.alg.treemap.MapModel; 
import in4arch.alg.treemap.Mappable;
import in4arch.alg.treemap.Rect; 
import in4arch.alg.treemap.*; 

class in4arch.alg.treemap.AbstractMapLayout implements MapLayout {

	public static var LAYOUT_SLICE_ALTERNATE = 1;
	public static var LAYOUT_SLICE_BEST = 2;
	public static var LAYOUT_PIVOT_BY_SIZE = 3;
	public static var LAYOUT_PIVOT_BY_SPLIT_SIZE = 4;
	public static var LAYOUT_PIVOT_BY_MIDDLE = 5;
	public static var LAYOUT_SQARIFIED = 6;
	public static var LAYOUT_STRIP_TREEMAP = 7;
	public static var LAYOUT_BINARY_TREE = 8;
	
	private static var LAYOUT_ALGORITMS:Array;
	
    // Flags for type of rectangle division and sort orders.
    public static var VERTICAL:Number = 0;
	public static var HORIZONTAL:Number = 1;
    public static var ASCENDING:Number = 0;
	public static var DESCENDING:Number = 1;
    
    /** Subclasses implement this method themselves. */
    function _layout(items:Array, bounds:Rect):Void{
		throw new Error("You must implement this method, abstract operation is not supported!");
	}
	
	/**
	 * Get layout by constant
	 * @param layout Number
	 * @return 
	 */
	public static function getLayout(layout:Number):MapLayout
	{
		switch (layout) {
			case LAYOUT_SLICE_ALTERNATE:
				return new SliceLayout(SliceLayout.ALTERNATE);
				break;
			case LAYOUT_SLICE_BEST:
				return new SliceLayout(SliceLayout.BEST);
				break;
			case LAYOUT_PIVOT_BY_SIZE:
				return new PivotBySize();
				break;
			case LAYOUT_PIVOT_BY_SPLIT_SIZE:
				return new PivotBySplitSize();
				break;
			case LAYOUT_PIVOT_BY_MIDDLE:
				return new PivotByMiddle();
				break;
			case LAYOUT_SQARIFIED:
				return new SquarifiedLayout();
				break;
			case LAYOUT_STRIP_TREEMAP:
				return new StripTreemap();
				break;
			case LAYOUT_BINARY_TREE:
				return new BinaryTreeLayout();
				break;
			default:
				trace ("Unknown layout constant: " + layout);
				return null;
		}
	}
	
	public static function getLayoutAlgorithms()
	{
		if (!LAYOUT_ALGORITMS) {
			LAYOUT_ALGORITMS = new Array(
				LAYOUT_SLICE_ALTERNATE, 
				LAYOUT_SLICE_BEST, 
				LAYOUT_PIVOT_BY_SIZE,
				LAYOUT_PIVOT_BY_SPLIT_SIZE,
				LAYOUT_PIVOT_BY_MIDDLE,
				LAYOUT_SQARIFIED,
				LAYOUT_STRIP_TREEMAP,
				LAYOUT_BINARY_TREE
			);
		}
		
		return LAYOUT_ALGORITMS;
	}
	
	/** Subclasses implement this method themselves. */
	public function getDescription():String{
		throw new Error("You must implement this method, abstract operation is not supported!");
		return null;
	}
	
	/** Subclasses implement this method themselves. */
	public function getName():String{
		throw new Error("You must implement this method, abstract operation is not supported!");
		return null;
	}	
    
    public function layout(model:MapModel, bounds:Rect):Void {
        _layout(model.getItems(), bounds);
    }

    public function totalSize(items_array:Array):Number {
        return _totalSize(items_array, 0, items_array.length-1);
    }
	
	public function _totalSize(items_array:Array, startIndex:Number, stopIndex:Number):Number{
        var sum:Number = 0;
        for (var i = startIndex; i <= stopIndex; i++){
            sum += items_array[i].getSize();
		}
        return sum;
    }
	
	// For a production system, use a quicksort...
    public function sortDescending(items_array:Array):Array {
        var temp_array:Array = new Array(items_array.length);
		//copy array
		for(var i = 0; i < temp_array.length; i++){
			temp_array[i] = items_array[i];
		}
		
		//do check
		var n:Number = temp_array.length;
        var outOfOrder:Boolean = true;
        while (outOfOrder) {
            outOfOrder = false;
            for (var i=0; i<n-1; i++) {
                var wrong:Boolean = (temp_array[i].getSize() < temp_array[i+1].getSize());
                if (wrong) {
                    var temp:Mappable = temp_array[i];
                    temp_array[i] = temp_array[i+1];
                    temp_array[i+1] = temp;
                    outOfOrder = true;
                }
            }
        }
        return temp_array;
    }

    function sliceLayout(items_array:Array, startIndex:Number, stopIndex:Number, bounds:Rect, orient:Number):Void {
        orderedSliceLayout(items_array, startIndex, stopIndex, bounds, orient, AbstractMapLayout.ASCENDING);
    }
    
    function orderedSliceLayout(items_array:Array, startIndex:Number, stopIndex:Number, bounds:Rect, orient:Number, order:Number):Void {
        var total:Number = _totalSize(items_array, startIndex, stopIndex);
        var a:Number = 0;
        var vertical:Boolean = (orient == AbstractMapLayout.VERTICAL);
		
        for (var i = startIndex; i <= stopIndex; i++) {
            var r:Rect = new Rect(0,0,1,1);
            var b:Number =items_array[i].getSize()/total;
            if (vertical) {
                r.x = bounds.x;
                r.w = bounds.w;
                if (order == AbstractMapLayout.ASCENDING){
                    r.y = bounds.y+bounds.h*a;
				} else {
                    r.y=bounds.y+bounds.h*(1-a-b);
				}
                r.h = bounds.h*b;
            } else {
                if (order==AbstractMapLayout.ASCENDING){
                    r.x=bounds.x+bounds.w*a;
				}else{
                    r.x=bounds.x+bounds.w*(1-a-b);
				}
                r.w = bounds.w*b;
                r.y = bounds.y;
                r.h = bounds.h;
            }
            items_array[i].setRect(r);
            a += b;
        }
		
    }
	
	public function layoutBest(items_array:Array, startIndex:Number, stopIndex:Number, bounds:Rect):Void {
		var ornt:Number = bounds.w > bounds.h ? AbstractMapLayout.HORIZONTAL : AbstractMapLayout.VERTICAL;
        sliceLayout(items_array, startIndex, stopIndex, bounds, ornt, AbstractMapLayout.ASCENDING);
    }
	
	
}
